#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 组合搜索功能模块
"""
组件提供的功能：主要关注Option函数
search_group = [
        Option('charge_type',
               is_multi=True,  需要多选
               text_func=lambda x: x[1], #choices是一个元组套元组对象，索引0是数值 1是中文文本
               value_func=lambda x:x[0]，#默认为0; 表示搜索时使用那个参数放入url的get请中：如x[0]为：/?charge_type=2  如x[1]为： /?charge_type=扣款
              ),  #  choices选项 choices=((1, "充值"), (2, "扣款"),(3, "创建订单"),(4, "删除订单"),(5, "撤销订单"),))
        Option(
            'customer',
            db_condition={"id__lte": 10, 'active': 1},  #db_condition=None表示展示所有数据；{"id__lte": 10, 'active': 1}表示数据库搜索条件：id小于10 且active字段等于1
            text_func=lambda x: x.username, #X是数据表中每条数据的对象，x.username 在HTML展示ForeignKey关联的表Customer 的用户名username:如：zhangya,....
            value_func=lambda x:x.id #默认为id搜索;  表示使用url的get请求 使用数据对象的id 为条件搜索数据
        ),  #TransactionRecord表custome 代表ForeignKey关联的数据表
]

"""
from django.db.models import ForeignKey, ManyToManyField


# 保存相关配置：
class Option(object):
    def __init__(self, field, is_condition=True, is_multi=False, db_condition=None, text_func=None, value_func=None):
        """
        :param field: 组合搜索关联的字段，有限的归类数据，穷举的字段不要使用这个方法
        :param is_multi: 是否支持多选
        :param db_condition: 数据库关联查询时的条件
        :param text_func: 此函数用于显示组合搜索按钮页面文本；必须传入函数，因为使用ForeignKey得到的是一个个数据对象，需要使用lambda函数处理需要展示什么字段
        :param value_func: 此函数用于显示组合搜索按钮值
        :param is_condition：True 表示是条件 如果改F 表示不是条件，那么选择对应条件后，无效，简单来说就是搜索功能无效，HTML展示有效
        """

        # self.field表示关联需要在HTML展示的，数据库的那个字段。
        # 如：TransactionRecord表的charge_type单选字段 里面有充值、扣款。。。需要展示在HTML界面用于充当搜索条件
        # 如果传入的field是外键字段，就是关联表的数据
        self.field = field
        self.is_condition = is_condition
        self.is_multi = is_multi
        if not db_condition:
            db_condition = {}
        self.db_condition = db_condition
        self.text_func = text_func
        self.value_func = value_func

        self.is_choice = False

    #作用：方便后面重写方法
    def get_db_condition(self, request, *args, **kwargs):
        return self.db_condition

    # 根据self.field参数去获取数据库关联的数据
    def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
        """
        model_class：传数据库的对象：如：models.TransactionRecord 为了获取里面的 ——charge_type——choices内容
        根据字段去获取数据库关联的数据
        :return:
        """
        # 根据gender或depart字符串，去自己对应的Model类中找到 字段对象
        #model_class._meta.get_field(字段名【如charge_type】) ：反射方法找到数据表对应的字段对象
        field_object = model_class._meta.get_field(self.field)

        title = field_object.verbose_name
        # 获取关联数据：ForeignKey
        #isinstance(field_object, ForeignKey) ：判断是否是field_object实例化的对象
        if isinstance(field_object, ForeignKey) or isinstance(field_object, ManyToManyField):
            # FK和M2M,应该去获取其关联表中的数据： QuerySet
            db_condition = self.get_db_condition(request, *args, **kwargs)
            # 返回一个类对象SearchGroupRow，把数据源（field_object.XXX）、配置对象（self），封装
            return SearchGroupRow(title,
                                  # 关联表 获取所有数据，QuerySet数据类型
                                  field_object.remote_field.model.objects.filter(**db_condition),
                                  self,
                                  request.GET)
        else:
            # 获取choice中的数据：元组
            self.is_choice = True
            return SearchGroupRow(title, field_object.choices, self, request.GET)
    #获取字段的文本数据
    def get_text(self, field_object):
        """
        获取文本函数
        :param field_object:
        :return:
        """
        #判断是否传传入了lambda函数，是就执行
        if self.text_func:
            return self.text_func(field_object)
        #如果是choice 我们取到第二个文本数据
        if self.is_choice:
            return field_object[1]

        return str(field_object)

    #生成url在get的查询条件
    def get_value(self, field_object):
        if self.value_func:
            return self.value_func(field_object)

        if self.is_choice:
            return field_object[0]
        # pk就是数据id
        return field_object.pk

    #构造条件 是否是多选，多选是in 非多选是=
    def get_search_condition(self, request):
        if not self.is_condition:
            return None
        #是多选
        if self.is_multi:
            values_list = request.GET.getlist(self.field)  # tags=[1,2]
            if not values_list:
                return None
            #用__in语法表示属于
            return '%s__in' % self.field, values_list
        else:
            value = request.GET.get(self.field)  # tags=[1,2]
            if not value:
                return None
            return self.field, value


class NbSearchGroup(object):
    # model_class 为数据库的类[models.数据表名]如：models.TransactionRecord
    #*options:传一个Option实例化对象如[可传多个]： Option('charge_type',is_multi = True),Option('customer',is_multi = True)
    def __init__(self, request, model_class, *options):
        self.request = request
        self.model_class = model_class
        self.options = options #一个元组

    def get_row_list(self):
        row_list = []
        # 循环便利出search_group列表的每个对象
        for option_object in self.options:
            # 传入数据库的表对象 models.TransactionRecord
            row = option_object.get_queryset_or_tuple(self.model_class, self.request)
            row_list.append(row) # 添加的是一个个SearchGroupRow()对象
        return row_list

    @property
    # 配置-构建搜索条件 产生charge_type的搜索条件 加property 执行时get_condition后面就不用加（）
    def get_condition(self):
        """
        获取组合搜索的条件
        :param request:
        :return:
        """
        condition = {}
        # ?depart=1&gender=2&page=123&q=999
        for option in self.options:
            key_and_value = option.get_search_condition(self.request)
            if not key_and_value:
                continue
            key, value = key_and_value
            condition[key] = value

        return condition


class SearchGroupRow(object):
    def __init__(self, title, queryset_or_tuple, option, query_dict):
        """
        :param title: 组合搜索的列名称
        :param queryset_or_tuple: 组合搜索关联获取到的数据
        :param option: 配置
        :param query_dict: request.GET
        """
        self.title = title
        self.queryset_or_tuple = queryset_or_tuple
        self.option = option
        self.query_dict = query_dict
    #实现循环类实例化对象时展示数据 并生成html
    def __iter__(self):
        # 生成器
        """<div class="whole">
                self.title
            </div>
            <div class="others"> .....

        """
        yield '<div class="whole">' #循环类对象时：优先输出html标签组成功能模块
        yield self.title
        yield '</div>'
        yield '<div class="others">'
        total_query_dict = self.query_dict.copy()
        total_query_dict._mutable = True

        origin_value_list = self.query_dict.getlist(self.option.field)
        if not origin_value_list:
            yield "<a class='active' href='?%s'>全部</a>" % total_query_dict.urlencode()
        else:
            total_query_dict.pop(self.option.field)
            yield "<a href='?%s'>全部</a>" % total_query_dict.urlencode()

        for item in self.queryset_or_tuple:
            text = self.option.get_text(item)
            value = str(self.option.get_value(item))
            query_dict = self.query_dict.copy()
            query_dict._mutable = True
            # 是否需要多选功能
            if not self.option.is_multi:  # 不需要
                query_dict[self.option.field] = value
                if value in origin_value_list:
                    query_dict.pop(self.option.field)
                    yield "<a class='active' href='?%s'>%s</a>" % (query_dict.urlencode(), text)
                else:
                    yield "<a href='?%s'>%s</a>" % (query_dict.urlencode(), text)
            else:
                # {'gender':['1','2']}
                multi_value_list = query_dict.getlist(self.option.field)
                if value in multi_value_list:
                    multi_value_list.remove(value)
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield "<a class='active' href='?%s'>%s</a>" % (query_dict.urlencode(), text)
                else:
                    multi_value_list.append(value)
                    query_dict.setlist(self.option.field, multi_value_list)
                    yield "<a href='?%s'>%s</a>" % (query_dict.urlencode(), text)

        yield '</div>'

#生成搜索条件[弃用] 优化后用 get_condition
def get_search_group_condition(search_group,request):
    condition = {}
    # ?depart=1&gender=2&page=123&q=999
    for option in search_group:
        key_and_value = option.get_search_condition(request)
        if not key_and_value:
            continue
        key, value = key_and_value
        condition[key] = value

    return condition